from PyQt4.QtCore import QObject

from game.boardModel import BoardModel
from autoplayer.score import *
from autoplayer.zobrist import Zobrist
from autoplayer.neighbour import has_neighbour
from config import *


class Evaluator:
    def __init__(self):
        self.model = None
        self.role = None
        self.zobrist = None
        self.manplayer_score = []
        self.autoplayer_score = []
        self.auto_max_score = SCORE.FIVE
        self.man_max_score = SCORE.FIVE
        self.cache = {}

    def setup(self, role, model, zobrist):
        self.model = model
        self.role = role
        self.zobrist = zobrist
        # self.zobrist.role = role
        self.manplayer_score = [[ROLES.vacant for i in range(Len)] for j in range(Len)]
        self.autoplayer_score = [[ROLES.vacant for i in range(Len)] for j in range(Len)]
        self.auto_max_score = SCORE.FIVE
        self.man_max_score = SCORE.FIVE
        self.cache = {}
        self.init_score()
        if self.role == ROLES.firsthand:
            self.zobrist.update(7, 7, self.role)

    def init_score(self):
        for i in range(0, Len):
            for j in range(0, Len):
                if self.model.get_pos(i, j) != ROLES.vacant:
                    continue
                if has_neighbour(self.model, i, j, 2, 2):
                    self.autoplayer_score[i][j] = self.evaluate_point(i, j, self.role)
                    self.manplayer_score[i][j] = self.evaluate_point(i, j, opposite_role(self.role))

    def evaluate_point(self, x0, y0, role):
        result = 0
        count = 0
        block = 0
        second_count = 0
        empty = -1
        upper = Len + 10
        lower = -10

        # vertical
        count = 1
        block = 0
        empty = -1
        second_count = 0
        for i in range(y0+1, upper, 1):
            if i >= Len:
                block += 1
                break
            t = self.model.get_pos(x0, i)
            if t == ROLES.vacant:
                if empty == -1 and i < Len - 1 and self.model.get_pos(x0, i+1) == role:
                    empty = count
                    continue
                else:
                    break
            if t == role:
                count += 1
                continue
            else:
                block += 1
                break
        for i in range(y0 - 1, lower, -1):
            if i < 0:
                block += 1
                break
            t = self.model.get_pos(x0, i)
            if t == ROLES.vacant:
                if empty == -1 and i > 0 and self.model.get_pos(x0, i - 1) == role:
                    empty = 0
                    continue
                else:
                    break
            if t == role:
                second_count += 1
                if empty != -1:
                    empty += 1
                continue
            else:
                block += 1
                break
        count += second_count
        result += get_type(count, block, empty)
        # horizontal
        count = 1
        block = 0
        empty = -1
        second_count = 0
        for i in range(x0 + 1, upper, 1):
            if i >= Len:
                block += 1
                break
            t = self.model.get_pos(i, y0)
            if t == ROLES.vacant:
                if empty == -1 and i < Len - 1 and self.model.get_pos(i+1, y0) == role:
                    empty = count
                    continue
                else:
                    break
            if t == role:
                count += 1
                continue
            else:
                block += 1
                break
        for i in range(x0 - 1, lower, -1):
            if i < 0:
                block += 1
                break
            t = self.model.get_pos(i, y0)
            if t == ROLES.vacant:
                if empty == -1 and i > 0 and self.model.get_pos(i - 1, y0) == role:
                    empty = 0
                    continue
                else:
                    break
            if t == role:
                second_count += 1
                if empty != -1:
                    empty += 1
                continue
            else:
                block += 1
                break
        count += second_count
        result += get_type(count, block, empty)
        # left lean
        count = 1
        block = 0
        empty = -1
        second_count = 0
        for i in range(1, upper, 1):
            x = x0 + i
            y = y0 + i
            if x >= Len or y >= Len:
                block += 1
                break
            t = self.model.get_pos(x, y)
            if t == ROLES.vacant:
                if empty == -1 and x < Len - 1 and y < Len - 1 and self.model.get_pos(x + 1, y + 1) == role:
                    empty = count
                    continue
                else:
                    break
            if t == role:
                count += 1
                continue
            else:
                block += 1
                break
        for i in range(1, upper, 1):
            x = x0 - i
            y = y0 - i
            if x < 0 or y < 0:
                block += 1
                break
            t = self.model.get_pos(x, y)
            if t == ROLES.vacant:
                if empty == -1 and x > 0 and y > 0 and self.model.get_pos(x - 1, y - 1) == role:
                    empty = 0
                    continue
                else:
                    break
            if t == role:
                second_count += 1
                if empty != -1:
                    empty += 1
                continue
            else:
                block += 1
                break
        count += second_count
        result += get_type(count, block, empty)
        # right lean
        count = 1
        block = 0
        empty = -1
        second_count = 0
        for i in range(1, upper, 1):
            x = x0 + i
            y = y0 - i
            if x < 0 or y < 0 or x >= Len or y >= Len:
                block += 1
                break
            t = self.model.get_pos(x, y)
            if t == ROLES.vacant:
                if empty == -1 and x < Len - 1 and y < Len - 1 and self.model.get_pos(x + 1, y - 1) == role:
                    empty = count
                    continue
                else:
                    break
            if t == role:
                count += 1
                continue
            else:
                block += 1
                break
        for i in range(1, upper, 1):
            x = x0 - i
            y = y0 + i
            if x < 0 or y < 0 or x >= Len or y >= Len:
                block += 1
                break
            t = self.model.get_pos(x, y)
            if t == ROLES.vacant:
                if empty == -1 and x > 0 and y > 0 and self.model.get_pos(x - 1, y + 1) == role:
                    empty = 0
                    continue
                else:
                    break
            if t == role:
                second_count += 1
                if empty != -1:
                    empty += 1
                continue
            else:
                block += 1
                break
        count += second_count
        result += get_type(count, block, empty)
        return modify_score(result)

    def _do_update(self, x, y):
        self.autoplayer_score[x][y] = self.evaluate_point(x, y, self.role)
        self.manplayer_score[x][y] = self.evaluate_point(x, y, opposite_role(self.role))

    def update_point(self):
        step = self.model.get_last_step()
        self.do_update_point(step[0], step[1])

    def do_update_point(self, x0, y0):
        r = 8
        # horizon
        for i in range(-r, r):
            x = x0
            y = y0 + i
            if y < 0 or self.model.get_pos(x, y) != ROLES.vacant:
                continue
            if y >= Len:
                break
            self._do_update(x, y)
        # vertical
        for i in range(-r, r):
            x = x0 + i
            y = y0
            if x < 0 or self.model.get_pos(x, y) != ROLES.vacant:
                continue
            if x >= Len:
                break
            self._do_update(x, y)
        # left lean
        for i in range(-r, r):
            x = x0 + i
            y = y0 + i
            if x < 0 or y < 0 or self.model.get_pos(x, y) != ROLES.vacant:
                continue
            if x >= Len or y >= Len:
                break
            self._do_update(x, y)
        # right lean
        for i in range(-r, r):
            x = x0 + i
            y = y0 - i
            if x < 0 or y < 0 or self.model.get_pos(x, y) != ROLES.vacant:
                continue
            if x >= Len or y >= Len:
                break
            self._do_update(x, y)

    def evaluate(self, role):
        if CACHE and self.cache.get(self.zobrist.code):
            return self.cache.get(self.zobrist.code)
        self.auto_max_score = -SCORE.FIVE
        self.man_max_score = -SCORE.FIVE
        for i in range(0, Len):
            for j in range(0, Len):
                if self.model.get_pos(i, j) != ROLES.vacant:
                    continue
                if self.auto_max_score < self.autoplayer_score[i][j]:
                    self.auto_max_score = self.autoplayer_score[i][j]
                if self.man_max_score < self.manplayer_score[i][j]:
                    self.man_max_score = self.manplayer_score[i][j]
        if role == self.role:
            result = self.auto_max_score - self.man_max_score
        else:
            result = self.man_max_score - self.auto_max_score
        self.cache[self.zobrist.code] = result
        return result
